package exec

import (
	"strconv"
	"strings"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/log"
	managecomm "hundsun.com/hsl/hschain/contract/native/manage/common"
	"hundsun.com/hsl/hschain/executor/common"
	"hundsun.com/hsl/hschain/protos/execute"
	protosmanage "hundsun.com/hsl/hschain/protos/manage"
)

const (
	// JudgeExecName Judge execution name
	JudgeExecName = "judge"
)

func init() {
	RegisterExecution(JudgeExecName, NewJudgeExecution)
}

// JudgeExecution 判断执行节点实例
type JudgeExecution struct {
	ExecutionAdaptor
}

// NewJudgeExecution 新建判断执行逻辑
func NewJudgeExecution(id, step uint32, proposal *protosmanage.ManageFlowProposal, properties *protosmanage.ExecProperties) Execution {
	var exec = &JudgeExecution{}
	exec.InitAdaptor(id, step, proposal, properties)
	return exec
}

// Run 执行判断节点
func (e *JudgeExecution) Run(ctx *common.ExecContent, gtx *execute.GroupedTransaction) error {
	var pass = true
	var err error
	if len(e.GetProperties().Conditions) > 0 {
		for index, cond := range e.GetProperties().Conditions {
			log.Debugf("begin to check %d condition", index)
			pass, err = e.checkConditions(ctx, cond)
			if err != nil {
				return err
			}

			// 需要判断所有的condition
			if e.GetProperties().AgreeAll {
				continue
			}
		}
	}
	if !pass {
		return managecomm.ErrExecutionNotPassed
	}
	return nil
}

func (e *JudgeExecution) checkConditions(ctx *common.ExecContent, cond *protosmanage.ExecCondition) (bool, error) {
	switch cond.Type {
	case protosmanage.ConditionType_RangeConditionType:
		val, err := strconv.Atoi(cond.ExpectVal)
		if err != nil {
			return false, err
		}
		if len(cond.LowerBound) == 0 && len(cond.UpperBound) == 0 && len(cond.LowerBoundEqual) == 0 &&
			len(cond.UpperBoundEqual) == 0 && len(cond.BoundEqual) == 0 {
			return false, managecomm.ErrLowerUpperBoundBothNil
		}
		// 判断下限，lowerBound
		if len(cond.LowerBound) > 0 {
			low, err := strconv.Atoi(cond.LowerBound)
			if err != nil {
				return false, err
			}
			if val <= low {
				// fmt.Printf("val:%d小于lowerBound:%d\n", val, low)
				return false, nil
			}
		}
		if len(cond.LowerBoundEqual) > 0 {
			le, err := strconv.Atoi(cond.LowerBoundEqual)
			if err != nil {
				return false, err
			}
			if val < le {
				// fmt.Printf("val:%d小于lowerBound:%d\n", val, low)
				return false, nil
			}
		}
		// 判断上限,upperBound包含等于
		if len(cond.UpperBound) > 0 {
			upper, err := strconv.Atoi(cond.UpperBound)
			if err != nil {
				return false, err
			}
			if val >= upper {
				return false, nil
			}
		}
		if len(cond.UpperBoundEqual) > 0 {
			ge, err := strconv.Atoi(cond.UpperBoundEqual)
			if err != nil {
				return false, err
			}
			if val > ge {
				return false, nil
			}
		}
		if len(cond.BoundEqual) > 0 {
			equal, err := strconv.Atoi(cond.BoundEqual)
			if err != nil {
				return false, err
			}
			if val != equal {
				return false, nil
			}
		}
		return true, nil
	case protosmanage.ConditionType_EqualConditionType:
		if len(cond.ExpectKey) == 0 && len(cond.ExpectVal) == 0 {
			return false, managecomm.ErrNilParam
		}

		var judgeVal string
		// 获取输入值
		if len(cond.ParamKey) > 0 {
			// TODO: 不同类型的值进行比较
			judgeInfo, err := ctx.Get([]byte(cond.ParamKey))
			if err != nil {
				return false, err
			}
			judgeVal = string(judgeInfo)
		}

		// 输入的期望值不为空，直接进行比较
		if len(cond.ExpectVal) > 0 {
			log.Infof("judge val '%s' with expect val '%s', condition_type: %d", judgeVal, cond.ExpectVal, cond.Type)
			return judgeVal == cond.ExpectVal, nil
		}

		// 获取期望值
		if len(cond.ExpectKey) > 0 {
			expectVal, err := ctx.Get([]byte(cond.ExpectKey))
			if err != nil {
				return false, err
			}
			log.Infof("judge val '%s' with expect val '%s', condition_type: %d", judgeVal, cond.ExpectVal, cond.Type)
			return judgeVal == string(expectVal), nil
		}
		return false, nil
	case protosmanage.ConditionType_ExistConditionType:
		if len(cond.TargetKey) == 0 && len(cond.TargetVals) == 0 {
			return false, managecomm.ErrNilParam
		}

		var judgeVal string
		// 获取输入值
		if len(cond.ParamKey) > 0 {
			// TODO: 不同类型的值进行比较
			judgeInfo, err := ctx.Get([]byte(cond.ParamKey))
			if err != nil {
				return false, err
			}
			judgeVal = string(judgeInfo)
		}

		if len(cond.TargetVals) > 0 {
			log.Infof("judge val '%s' with expect list '%v', condition_type: %d", judgeVal, cond.TargetVals, cond.Type)
			for _, targetVal := range cond.TargetVals {
				if strings.EqualFold(judgeVal, targetVal) {
					return true, nil
				}
			}
		}

		if len(cond.TargetKey) > 0 {
			var expectVals = &protosmanage.ManageFlowValidatorInfo{}
			expectInfo, err := ctx.Get([]byte(cond.GetTargetKey()))
			if err != nil {
				return false, err
			}
			err = proto.Unmarshal(expectInfo, expectVals)
			if err != nil {
				log.Errorf("unmarshal expect validators failed, err: %s", err.Error())
			}

			log.Infof("judge val '%s' with expect list '%v', condition_type: %d", judgeVal, expectVals.Validators, cond.Type)
			for _, val := range expectVals.Validators {
				if strings.EqualFold(judgeVal, val) {
					log.Infof("")
					return true, nil
				}
			}
		}
		return false, nil
	case protosmanage.ConditionType_PercentConditionType:
		if cond.ExpectPercent == 0 {
			log.Error("invalid expect percent")
			return false, managecomm.ErrNilParam
		}

		var validatorInfo = &protosmanage.ManageFlowValidatorInfo{}
		validatorBytes, err := ctx.Get([]byte(cond.ParamKey))
		if err != nil {
			return false, err
		}
		err = proto.Unmarshal(validatorBytes, validatorInfo)
		if err != nil {
			return false, err
		}

		log.Infof("get validator info from db, %v", validatorInfo.Validators)

		var totalValidator int
		if cond.ExpectVal != "" {
			totalValidator = len(cond.ExpectVal)
		} else if cond.ExpectKey != "" {
			expectValInfo, err := ctx.Get([]byte(cond.ExpectKey))
			if err != nil {
				if err != nil {
					log.Errorf("get expect info from db failed, err: %s", err.Error())
					return false, err
				}
			}
			var expectVals = &protosmanage.ManageFlowGroupInfo{}
			err = proto.Unmarshal(expectValInfo, expectVals)
			if err != nil {
				log.Errorf("unmarshal expect validator info failed, err: %s", err.Error())
				return false, err
			}
			totalValidator = len(expectVals.Addresses)
		} else {
			log.Error("expect param is nil")
			return false, managecomm.ErrNilParam
		}
		percent := float32(len(validatorInfo.Validators)) / float32(totalValidator)

		if percent >= cond.ExpectPercent {
			log.Infof("check successfully")
			return true, nil
		}
		log.Infof("check failed, current percent %f is less than expect percent %f", percent, cond.ExpectPercent)
		return false, managecomm.ErrExecutionNotPassed
	default:
		return false, managecomm.ErrUnknownConditionType
	}
}
